/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package wwcontrolserver.controls;

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.*;
import wwcontrolserver.datarecorders.WWControlServerLogger;

/**
 *
 * @author Michael Ferry
 */
public abstract class AbstractDutyCycleControl extends AbstractControl {
    //if the scheduled control is to begin at a future time, how should the digital output be
    //set in the interum?
    public static final boolean ScheduledControlDefaultInitialOutputState = false;
    public static final int DefaultThreadPoolSize = 1;
    private DutyCycleControlState CurrentState; 

    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(DefaultThreadPoolSize);
    private ScheduledFuture<?> OnModulatorHandle = null;
    private ScheduledFuture<?> OffModulatorHandle = null;
    
    public AbstractDutyCycleControl(DutyCycleControlState InitialState, WWControlServerLogger WWCSLog){
        super(InitialState, WWCSLog);
        this.CurrentState = InitialState;
    }
 
    @Override
    public void SetControlState(AbstractControlState State) throws Exception {
        CurrentState.UpdateControlState(State);
        UpdateDutyCycleControlState();
    }
     
    public final void UpdateDutyCycleControlState() throws Exception {

        //Cancel currently scheduled operation
        CancelScheduledOperation();
        switch (CurrentState.OpMode) {
            case CONTINUOUS:
                SetBinaryOutputState(true);
                WWCSLog.RecordLogEntry("Starting continuous operation: " + CurrentState.ControlName);
                break;
            case SCHEDULED:
                CurrentState = BeginScheduledOperation(CurrentState);
                break;
            case OFF:
                SetBinaryOutputState(false);
                WWCSLog.RecordLogEntry("Turning off: " + CurrentState.ControlName);
                break;
        }
    }
    
    @Override
    public AbstractControlState GetControlState(){
        return CurrentState;
    }
    //This simply cancels the scheuled control threads - it does not actuate the control on or off!
    //The other control functions must determine if the new control regimine requires the output
    //to be actuated and act accordingly
    protected void CancelScheduledOperation() {
        if(OnModulatorHandle != null){
            if(!OnModulatorHandle.isDone()){
                OnModulatorHandle.cancel(true);
                OnModulatorHandle = null;
                WWCSLog.RecordLogEntry("Ending scheduled control for: " + CurrentState.ControlName);
            }
        }
        if(OffModulatorHandle != null){
            if(!OffModulatorHandle.isDone()){
                OffModulatorHandle.cancel(true);
                OffModulatorHandle = null;
            }
        }
    }
    
    protected DutyCycleControlState BeginScheduledOperation(DutyCycleControlState State) throws Exception {
        
        final Runnable OnModulator = new Runnable() {
            @Override
            public void run() {
                try {
                    SetBinaryOutputState(true);
                    WWCSLog.RecordLogEntry("Turning on: " + CurrentState.ControlName);
                } catch (Exception e) {
                    WWCSLog.RecordExceptionLogEntry(e);
                }
            }
        };

        final Runnable OffModulator = new Runnable() {
            @Override
            public void run() {
                //ModulatorHandle.cancel(true);
                try {
                    SetBinaryOutputState(false);
                    WWCSLog.RecordLogEntry("Turning off: " + CurrentState.ControlName);
                } catch (Exception e) {
                    WWCSLog.RecordExceptionLogEntry(e);
                }

            }
        };
        if (State.ScheduledControlStartTimeMilli == null) {
            State.SetScheduledControlStartTime();
            
            //Start the OffModulator after a initial delay of the OnPeriod and then run again after TotalPeriod time units
            OnModulatorHandle = scheduler.scheduleAtFixedRate(OnModulator, 0, State.DutyCyclePeriodMilli, TimeUnit.MILLISECONDS);
            OffModulatorHandle = scheduler.scheduleAtFixedRate(OffModulator, State.OnTimePeriodMilli, State.DutyCyclePeriodMilli, TimeUnit.MILLISECONDS);
        } else {
            long CurrentTimeMilli = System.currentTimeMillis();
            long initialDelayMilli = GetInitialDelay(CurrentTimeMilli, State.ScheduledControlStartTimeMilli, State.DutyCyclePeriodMilli);
            OnModulatorHandle = scheduler.scheduleAtFixedRate(OnModulator, initialDelayMilli, State.DutyCyclePeriodMilli, TimeUnit.MILLISECONDS);
            //Start the OffModulator after a initial delay of the OnPeriod and then run again after TotalPeriod time units
            OffModulatorHandle = scheduler.scheduleAtFixedRate(OffModulator, initialDelayMilli + State.OnTimePeriodMilli, State.DutyCyclePeriodMilli, TimeUnit.MILLISECONDS);
            DetermineScheduledControlOutputStateAtCurrentTime(CurrentTimeMilli, State.ScheduledControlStartTimeMilli,
                    State.DutyCyclePeriodMilli, State.OnTimePeriodMilli);
        }
//        //Start the OnModulator immediately and run it every TotalPeriod time units
//        OnModulatorHandle = scheduler.scheduleAtFixedRate(OnModulator, initialDelay, TotalPeriod, Tunit);
//        //Start the OffModulator after a initial delay of the OnPeriod and then run again after TotalPeriod time units
//        OffModulatorHandle = scheduler.scheduleAtFixedRate(OffModulator, initialDelay + OnPeriod, TotalPeriod, Tunit);
        WWCSLog.RecordLogEntry("Starting scheduled control for: " + CurrentState.ControlName +
                " On period: " + State.OnTimePeriod + " Total Period: " + State.DutyCyclePeriod + " Time unit: " + State.Tunit.toString());
        return State;

    }
    protected long GetInitialDelay(long CurrentTimeMilli, long StartTimeMilli, long DutyCyclePeriodMilli){
        long EllapsedTime = CurrentTimeMilli - StartTimeMilli;
        //If EllapsedTime is less than zero, that means we have a future start time.  The inital delay
        //is then equal to the absolute value of EllapsedTime
        if(EllapsedTime < 0){
            return Math.abs(EllapsedTime);
        }else{
            //If Ellapsed Time is positive the start time was in the past.  First find the point we are at
            //in the cycle just as we would in the DetermineScheduledControlOutputStateAtCurrentTime function.
            //Then subtract this time from the DutyCyclePeriodMilli to find the initial delay
            long CycleTime = EllapsedTime % DutyCyclePeriodMilli;
            return DutyCyclePeriodMilli - CycleTime;
        }       
    }
    
    protected void DetermineScheduledControlOutputStateAtCurrentTime(long CurrentTimeMilli, long StartTimeMilli,  
            long DutyCyclePeriodMilli, long OnTimePeriodMilli) throws Exception {
        
        long EllapsedTime = CurrentTimeMilli - StartTimeMilli;
        if (EllapsedTime <= 0) {
            SetBinaryOutputState(ScheduledControlDefaultInitialOutputState);
        } else {
            if (EllapsedTime % DutyCyclePeriodMilli < OnTimePeriodMilli) {
                SetBinaryOutputState(true);
                WWCSLog.RecordLogEntry("Turning on: " + CurrentState.ControlName);
            } else {
                SetBinaryOutputState(false);
                WWCSLog.RecordLogEntry("Turning off: " + CurrentState.ControlName);
            }
        }
    }
    
    @Override
    public void NotifyControl() {
        
        String NotificationString = "Control Notified " + this.GetControlName();
        System.out.println(NotificationString);
        WWCSLog.RecordLogEntry(NotificationString);
        
        WWCSLog.RecordLogEntry("Control: " + CurrentState.ControlName + " Notified");
        try {

            switch (CurrentState.OpMode) {
                case CONTINUOUS:
                    // SetContinuousOperation();
                    SetBinaryOutputState(true);
                    WWCSLog.RecordLogEntry("Starting continuous operation: " + CurrentState.ControlName);
                    break;
                case SCHEDULED:
                    DetermineScheduledControlOutputStateAtCurrentTime(System.currentTimeMillis(), CurrentState.ScheduledControlStartTimeMilli,
                            CurrentState.DutyCyclePeriodMilli, CurrentState.OnTimePeriodMilli);
                    break;
                case OFF:
                    SetBinaryOutputState(false);
                    WWCSLog.RecordLogEntry("Turning off: " + CurrentState.ControlName);
                    break;
            }

        } catch (Exception e) {
            WWCSLog.RecordExceptionLogEntry(e);
        }
    }
    
    @Override
    public void ShutdownControl() {
        CurrentState.SetOffState();
        try {
            UpdateDutyCycleControlState();
        } catch (Exception e) {
            WWCSLog.RecordExceptionLogEntry(e);
        }
        scheduler.shutdown();
    }
    protected abstract void SetBinaryOutputState(boolean State) throws Exception;
    public abstract boolean GetBinaryOutputState() throws Exception;
}
